#pragma once

#include "fsa.hpp"
#include "../commons/token.hpp"
#include <istream>
#include <ostream>
#include <sstream>
#include <stack>

using std::cout;
using std::endl;
using std::istream;
using std::ostream;
using std::stack;
using std::stringstream;

#define __THROW_INVALID_SYNTAX(reg)                                                      \
    std::cerr << "Invalid regex syntax: " + reg;                                         \
    throw std::length_error("Invalid regex syntax: " + reg);

class MyLexer {
private:
    FsaSharedField _shared;
    FSA _dfaBase;
    enum class State { CHAR, OR, BRACE, BRACE_OR, BRACKET, BRACKET_TO, ESCAPE };

public:
    MyLexer() : _shared(FsaSharedField()), _dfaBase(FSA::makeBase(&_shared)) {}
    void clear() { _shared.clear(); }

public:
    /**
     * Match the given string
     * @param str the string to match
     * @param skipws skip whitespace between tokens
     * @param line line number
     * @throw ParseException if syntax error
     * @return matched token name
     */
    vector<Token> lex(const string &str, bool skipws = true) const {
        stringstream ss;
        ss << str;
        return lex(ss, skipws);
    }

    /**
     * Match the given string from stream
     * @param iss input stream
     * @param skipws skip whitespace between tokens
     * @param line line number
     * @return matched token name
     * @throw ParseException if syntax error
     */
    vector<Token> lex(istream &iss, bool skipws = true) const {
        Position curPos;
        vector<Token> ret;
        char ch;
        Token token;
        constexpr auto isWS = [](char c) { return c == ' ' || c == '\t' || c == '\n'; };
        const int BEGIN_STATE = _dfaBase.getBeginState();
        optional<int> state = BEGIN_STATE;
        bool advance = true;
        bool breakwhile = false;
        iss >> std::noskipws;
        while (true) {
            if (advance) {
                if (!(iss >> ch)) {
                    breakwhile = true;
                    ch = __FAKE_END;
                }
                curPos.col++;
            }
            if ((state == BEGIN_STATE) && skipws && isWS(ch)) {
                token.startPos = curPos;
                advance = true;
                if (ch == '\n') {
                    curPos.line++;
                    curPos.col = 0;
                }
                if (breakwhile)
                    break;
                continue;
            }
            token.word += ch;
            int oldState = state.value();
            advance = _shared.advance(oldState, ch);
            state = _shared.transit(oldState, ch);
            if (!state) { // no such transition
                auto tokenName = _dfaBase.getAcName(oldState);
                if (tokenName) { // is ac state
                    state = BEGIN_STATE;
                    token.word.pop_back();
                    token.endPos = curPos;
                    token.tokenName = tokenName.value();
                    ret.push_back(token);
                    token.clear();
                } else if ((ch == __FAKE_END) && (oldState == BEGIN_STATE)) { // trailing \n
                    break;
                } else { // isn't ac state
                    std::cerr << "Invalid token at line " + std::to_string(curPos.line) +
                                     "[" + std::to_string(curPos.col) + "]: \'"+ ch + "\'\n";
                    throw ParseException(curPos.line, curPos.col, "Invalid token");
                }
            }
            if (breakwhile)
                break;
        } // Main Loop
        return ret;
    }

    /**
     * Add regexes from map
     */ 
    template<class FwdIter>
    void addRegex(FwdIter first, FwdIter last){
        for (int priority = 0; first != last; ++first, ++priority) {
            addRegex(first->first, first->second, priority);
        }
    }

    /**
     * Add a regex and its token name to FSA
     * @param tokenName token's name
     * @param reg regex
     */
    void addRegex(const string &tokenName, const string &reg, const int priority) {
        static constexpr auto isOperator = [](char ch) {
            constexpr char operators[] = "()[]|*+?.\\";
            return std::any_of(operators, operators + 10, [ch](auto c) { return ch == c; });
        };
        stack<FSA> fsa;
        stack<State> state;
        acceptableChars actmp;
        char stChar;
        bool legalOp = false;           // Don't allow adjacent +*?，[-，start+*?，
        bool legalOr = false;           // Don't allow adjacent || (| |） start|
        bool helpMeReduceBrace = false; // lookahead
        fsa.push(FSA(&_shared, tokenName, priority));
        state.push(State::CHAR);
        // process \([*+?, return true if char is one of \([*+?
        auto doOp = [&fsa, &state, &helpMeReduceBrace, &legalOp, tokenName, priority,
                     this](char ch, bool frombegin) {
            if (ch == '.') {
                legalOp = false;
                fsa.top().appendState(acceptableChars::MetaChar::DOT);
            } else if (ch == '(') {
                state.push(State::BRACE);
                fsa.push(FSA(&_shared, tokenName, priority));
            } else if (ch == '[') {
                legalOp = false;
                state.push(State::BRACKET);
            } else if (ch == '\\'){
                legalOp = false;
                state.push(State::ESCAPE);
            } else if (legalOp && (ch == '*' || ch == '+' || ch == '?')) {
                fsa.top().performOp(ch, frombegin && helpMeReduceBrace);
                if (helpMeReduceBrace) {
                    helpMeReduceBrace = false;
                    auto rhs = fsa.top();
                    fsa.pop();
                    fsa.top().concatOp(rhs);
                    state.pop();
                }
            } else {
                legalOp = true;
                return false;
            }
            legalOp ^= true;
            return true;
        };
        // =============================Main Loop=================================== //
        for (int __ = 0; __ < reg.size(); ++__) {
            auto ch = reg[__];
#ifdef __DEBUG
            std::cout << ch << std::endl;
#ifdef __DEBUG_LV2
            for (auto i : fsa.top().selfStates) {
                cout << i << ", ";
            }
            cout << endl;
            printStates(*_shared);
            std::cout << std::endl;
#endif
#endif
            bool tmpOr = true;
            switch (state.top()) {
            case State::CHAR:
                if (doOp(ch, false)) {
                    // ([*+?
                } else if (ch == '|' && legalOr) {
                    tmpOr = false;
                    fsa.push(FSA(&_shared, tokenName, priority));
                    state.push(State::OR);
                } else if (isOperator(ch)) { // )]
                    __THROW_INVALID_SYNTAX(reg)(reg);
                } else {
                    fsa.top().appendState({ch});
                }
                break;
            case State::OR:
                if (doOp(ch, false)) {
                    // ([*+?
                } else if (ch == '|' && legalOr) { // reduce OR
                    tmpOr = false;
                    auto rhs = fsa.top();
                    fsa.pop();
                    fsa.top().orOp(rhs);
                    fsa.push(FSA(&_shared, tokenName, priority));
                } else if (isOperator(ch)) { // )]
                    __THROW_INVALID_SYNTAX(reg);
                } else {
                    fsa.top().appendState({ch});
                }
                break;
            case State::BRACE:
                if (doOp(ch, true)) {
                    // ([*+?
                } else if (ch == '|' && legalOr) {
                    tmpOr = false;
                    fsa.push(FSA(&_shared, tokenName, priority));
                    state.push(State::BRACE_OR);
                } else if (ch == ')') { // reduce brace
                    // lookahead for *+?
                    if (__ + 1 < reg.size() &&
                        (reg[__ + 1] == '*' || reg[__ + 1] == '+' ||
                         reg[__ + 1] == '?')) {
                        helpMeReduceBrace = true;
                        // help me reduce brace after reduce *+?
                    } else { // reduce
                        auto rhs = fsa.top();
                        fsa.pop();
                        fsa.top().concatOp(rhs);
                        state.pop();
                    }
                } else if (isOperator(ch)) { // ]
                    __THROW_INVALID_SYNTAX(reg);
                } else {
                    fsa.top().appendState({ch});
                }
                break;
            case State::BRACE_OR:
                if (doOp(ch, false)) {
                    // ([*+?
                } else if (ch == '|' && legalOr) { // reduce OR
                    tmpOr = false;
                    auto rhs = fsa.top();
                    fsa.pop();
                    fsa.top().orOp(rhs);
                    fsa.push(FSA(&_shared, tokenName, priority));
                } else if (ch == ')' && legalOr) {
                    // reduce OR
                    auto rhs = fsa.top();
                    fsa.pop();
                    fsa.top().orOp(rhs);
                    state.pop();
                    // reduce brace look ahead
                    if (__ + 1 < reg.size() &&
                        (reg[__ + 1] == '*' || reg[__ + 1] == '+' ||
                         reg[__ + 1] == '?')) {
                        helpMeReduceBrace = true;
                        // help me reduce brace after reduce *+?
                    } else { // reduce
                        auto rhs = fsa.top();
                        fsa.pop();
                        fsa.top().concatOp(rhs);
                        state.pop();
                    }
                } else if (isOperator(ch)) { // ]
                    __THROW_INVALID_SYNTAX(reg);
                } else {
                    fsa.top().appendState({ch});
                }
                break;
            case State::BRACKET:
                tmpOr = true;
                if (ch == '-' && __ > 0 && reg[__ - 1] != '[') {
                    state.push(State::BRACKET_TO);
                } else if (ch == ']') { // reduce bracket
                #ifdef __DEBUG
                    __PRINT_LINE_HINT("MODIFY EXIST!!!!!!!");
                #endif
                    actmp.setExist(true);
                    fsa.top().appendState(actmp);
                    state.pop();
                    actmp.clear();
                } else if (ch == '^' && reg[__ - 1] == '[') {
                    actmp.setExist(false);
                } else if (ch == '^' && reg[__ - 1] != '[') {
                    __THROW_INVALID_SYNTAX(reg);
                } else {
                    actmp.insert(ch);
                    stChar = ch;
                }
                break;
            case State::BRACKET_TO:
                if (isOperator(ch) || ch - stChar < 0) {
                    __THROW_INVALID_SYNTAX(reg);
                } else {
                    for (auto i = stChar; i <= ch; ++i) {
                        actmp.insert(i);
                    }
                    state.pop();
                }
                break;
            case State::ESCAPE:
                if(isOperator(ch)){
                    fsa.top().appendState({ch});
                    state.pop();
                } else {
                    __THROW_INVALID_SYNTAX(reg);
                }
                break;
            default:
                __PRINT_LINE_HINT("Miss a condition");
                throw std::logic_error("Miss a condition in MyLexer.addRegex()");
            }
            legalOr = tmpOr;
        }                               // Main Loop
        if (state.top() == State::OR) { // reduce OR
            auto rhs = fsa.top();
            fsa.pop();
            fsa.top().orOp(rhs);
        } else if (helpMeReduceBrace) {
            // DEL I guess this line will never reach, but I don't dare to remove it
            helpMeReduceBrace = false;
            auto rhs = fsa.top();
            fsa.pop();
            fsa.top().concatOp(rhs);
            state.pop();
        } else if (state.top() != State::CHAR) {
            __THROW_INVALID_SYNTAX(reg);
        }
        FSA machine = fsa.top();
        machine.convertDFA(machine.getTokenInfo());
        _dfaBase.mergeBegin(machine);
    }

public:
    const FSA &getDFABase() const { return _dfaBase; }
    const FsaSharedField &getSharedField() const { return _shared; }
};